package cn.regexp.auth.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.regexp.auth.domain.LoginLog;
import cn.regexp.auth.domain.UserLogin;
import cn.regexp.auth.domain.UserResponse;
import cn.regexp.auth.enums.UserRedisKeyEnum;
import cn.regexp.auth.web.msg.AuthCodeMsg;
import cn.regexp.auth.mapper.UserMapper;
import cn.regexp.auth.mq.MQConstant;
import cn.regexp.auth.service.IUserService;
import cn.regexp.auth.util.MD5Util;
import cn.regexp.common.core.domain.UserInfo;
import cn.regexp.common.core.exception.BusinessException;
import cn.regexp.common.redis.enums.CommonRedisKeyEnum;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * @author Regexpei
 * @date 2024/7/4 19:42
 * @description 用户服务实现类
 */
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    private UserLogin getUser(Long phone) {
        UserLogin userLogin;
        // userHash
        String hashKey = UserRedisKeyEnum.USER_HASH.getRealKey("");
        // userZset
        String zSetKey = UserRedisKeyEnum.USER_ZSET.getRealKey("");
        String userKey = String.valueOf(phone);
        String objStr = (String) redisTemplate.opsForHash().get(hashKey, String.valueOf(phone));

        if (StringUtils.isEmpty(objStr)) {
            // 缓存中并没有，从数据库中查询
            userLogin = userMapper.selectUserLoginByPhone(phone);
            // 把用户的登录信息存储到 Hash 结构中
            redisTemplate.opsForHash().put(hashKey, userKey, JSON.toJSONString(userLogin));
            // 使用 zset 结构，value 存用户手机号码，分数为登录时间，在定时器中找出7天前登录的用户，然后在缓存中删除
            // 我们缓存中的只存储7天的用户登录信息(热点用户)
        } else {
            // 缓存中有这个key
            userLogin = JSON.parseObject(objStr, UserLogin.class);
        }

        redisTemplate.opsForZSet().add(zSetKey, userKey, System.currentTimeMillis());
        return userLogin;
    }

    @Override
    public UserResponse login(Long phone, String password, String ip, String token) {
        // 无论登录成功还是登录失败，都需要进行日志记录
        LoginLog loginLog = new LoginLog(phone, ip, new Date());

        rocketMQTemplate.sendOneWay(MQConstant.LOGIN_TOPIC, loginLog);
        // 根据用户手机号码查询用户对象
        UserLogin userLogin = this.getUser(phone);

        /*
            进行密码加盐比对
         */
        // 前一个成立，userLogin == null 说明数据库中没有该用户的信息（因为是通过账号查找用户的）
        // 后一个成立，说明用户密码输入有误
        if (userLogin == null || !userLogin.getPassword().equals(MD5Util.encode(password, userLogin.getSalt()))) {
            // 进入这里说明登录失败
            loginLog.setState(LoginLog.LOGIN_FAIL);
            // 往MQ中发送消息,登录失败 LOGIN_TOPIC:false
            rocketMQTemplate.sendOneWay(MQConstant.LOGIN_TOPIC + ":" + LoginLog.LOGIN_FAIL, loginLog);
            // 同时抛出异常，提示前台账号密码有误
            throw new BusinessException(AuthCodeMsg.LOGIN_ERROR);
        }

        // 如果token还在有效期之内就不在进行登录操作了
        UserInfo userInfo = getByToken(token);

        /*
            Redis缓存userInfo
        */
        // 走到这里，说明用户的账号密码输入无误
        // 判断从 redis 中取出的 userInfo 是否为空，若为空，则表示用于还没有登录，则从数据库中查出用户信息并缓存到 redis 中
        if (userInfo == null) {
            // 查询
            userInfo = userMapper.selectUserInfoByPhone(phone);
            userInfo.setLoginIp(ip);
            // 创建 token，并缓存用户信息，token用于响应给前台
            token = createToken(userInfo);
            rocketMQTemplate.sendOneWay(MQConstant.LOGIN_TOPIC, loginLog);
        }
        return new UserResponse(token, userInfo);
    }

    private String createToken(UserInfo userInfo) {
        // token创建
        String token = IdUtil.fastSimpleUUID();
        // 把user对象存储到redis中
        CommonRedisKeyEnum redisKey = CommonRedisKeyEnum.USER_TOKEN;
        redisTemplate.opsForValue()
                .set(redisKey.getRealKey(token), JSON.toJSONString(userInfo), redisKey.getExpireTime(),
                        redisKey.getUnit());
        return token;
    }

    /**
     * 根据传入的token获取UserInfo对象
     *
     * @param token token
     * @return 用户信息
     */
    private UserInfo getByToken(String token) {
        // 通过token从redis中查询userinfo对象
        String strObj = redisTemplate.opsForValue().get(CommonRedisKeyEnum.USER_TOKEN.getRealKey(token));

        if (StringUtils.isEmpty(strObj)) {
            return null;
        }

        return JSON.parseObject(strObj, UserInfo.class);
    }
}
